package geometry;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.media.j3d.Appearance;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.Node;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Matrix4d;
import javax.vecmath.Vector3d;
import constraint.Variable;
import constraint.VariableType;
import foundationgeom.Axis;
import foundationgeom.EulerAngles;
import foundationgeom.Point;
import foundationgeom.Tolerance;
import foundationgeom.Transformation;
import foundationgeom.Vector;
import graph.AbstractNode;
import graph.GNode;
import graph.NodeType;

public class Box extends AbstractNode implements Geometry, GNode {
    private Variable xdim;
	private Variable ydim;
	private Variable zdim;
	private CoordSys cs;
	public String name="";
	
	public String toString() {
	    String s = "\n" + name + " " + super.getLabelsString();//+ "xdim="+xdim+", ydim="+ydim+", zdim="+zdim;
        s=s.concat("\n"+cs.toString());
	    return s;
	}
    
	/**
	 * Builds a box in the world CS
	 * @param xdim
	 * @param ydim
	 * @param zdim
	 * @param label
	 */
	public Box(double xdim, double ydim, double zdim, String label) {
        super(NodeType.PART, label);   
        cs = new CoordSys(World.getCS(), this);
        this.xdim = new Variable(xdim, VariableType.BOX_X_DIM, this, name);
        this.ydim = new Variable(ydim, VariableType.BOX_Y_DIM, this, name);
        this.zdim = new Variable(zdim, VariableType.BOX_Z_DIM, this, name);
    }

	public Box(double xdim, double ydim, double zdim, Collection<String> labels) {
	    super(NodeType.PART, labels);   
	    cs = new CoordSys(World.getCS(), this);
	    this.xdim = new Variable(xdim, VariableType.BOX_X_DIM, this, name);
        this.ydim = new Variable(ydim, VariableType.BOX_Y_DIM, this, name);
        this.zdim = new Variable(zdim, VariableType.BOX_Z_DIM, this, name);
	}
	
	public Box(double xdim, double ydim, double zdim, Collection<String> labels, CoordSys parent) {
	    super(NodeType.PART, labels);   
	    cs = new CoordSys(parent, this);
	    this.xdim = new Variable(xdim, VariableType.BOX_X_DIM, this, name);
	    this.ydim = new Variable(ydim, VariableType.BOX_Y_DIM, this, name);
	    this.zdim = new Variable(zdim, VariableType.BOX_Z_DIM, this, name);
	}
	
    public Transformation getTransformation(Geometry geometry) {
	    return this.cs.getTransformationTo(geometry.getCoordSys()); 
	}
		
	public void translateBy(double dx, double dy, double dz) {
		cs.translate(Vector.create(dx,dy,dz));
	}
	
	public void print() {
		System.out.println("xdim="+xdim+", ydim="+ydim+", zdim="+zdim);
		System.out.println(cs.toString());
	}
		
	public String toVRML() {
        CoordSys center = new CoordSys(this.getCoordSys(), null);
        center.translate(Vector.create(
                xdim.getFloatValue()/2.0,
                ydim.getFloatValue()/2.0, 
                zdim.getFloatValue()/2.0));
        center.changeParentTo(World.getCS());
        Vector o = center.getOriginDisplacement();
        String s="";
        s = s+"Transform {\n";
        s = s+"  translation ";
        s = s + o.getX();
        s = s + " ";
        s = s + o.getY();
        s = s + " ";
        s = s + o.getZ();
        s = s + "  \nchildren[";
        
        s = s+"Transform {\n";
        s = s+"  rotation 1.0 0.0 0.0 ";
        s = s + Math.toRadians(center.getEulerAngles().getPitch());
        s = s + "  \nchildren[\n";
        
        s = s+"Transform {\n";
        s = s+"  rotation 0.0 1.0 0.0 ";
        s = s + Math.toRadians(center.getEulerAngles().getBank());
        s = s + "  \nchildren[\n";

        s = s+"Transform {\n";
        s = s+"  rotation 0.0 0.0 1.0 ";
        s = s + Math.toRadians(center.getEulerAngles().getHeading());
        s = s + "  \nchildren[\n";
        
        s = s + "  \n\tShape {\n\t\tappearance Appearance {\n\t\t\tmaterial Material {";
        s = s + "\n\t\t\t\temissiveColor 1 1 0 \n\t\t\t\tambientIntensity 8 \n\t\t\t\tspecularColor 1 1 0 \n\t\t\t\ttransparency 0\n\t\t\t}\n\t\t}";
        s = s + "\n\t\tgeometry Box{";
        s = s + "\n\t\tsize ";
        s = s + xdim.getDoubleValue();
        s = s + " ";
        s = s + ydim.getDoubleValue();
        s = s + " ";
        s = s + zdim.getDoubleValue();
        s = s + "\n\t\t}\n\t\t}\n\t]\n\t\n}";
        
        s = s + "\n]\n}";
        s = s + "\n]\n}";
        s = s + "\n]\n}";
        return s;
    }
	
	private com.sun.j3d.utils.geometry.Box getJava3DBox(float red, float blue, float green, float scale) {
        Appearance a = new Appearance();
        a.setColoringAttributes(new ColoringAttributes(red,blue,green, ColoringAttributes.NICEST));
        com.sun.j3d.utils.geometry.Box b = new com.sun.j3d.utils.geometry.Box(
                xdim.getFloatValue()/2.0f/scale, 
                ydim.getFloatValue()/2.0f/scale, 
                zdim.getFloatValue()/2.0f/scale, a);
        return b;
    }

    /* (non-Javadoc)
     * @see geometry.Geometry#getTransormedJava3DBox(float, float, float, float)
     */
    public List<Node> getTransormedJava3DBox(float red, float blue, float green, float scale) {
        Matrix4d matrix = new Matrix4d();
        matrix.setIdentity();
        CoordSys center = new CoordSys(this.getCoordSys(), null);
        center.translate(Vector.create(
                xdim.getFloatValue()/2.0,
                ydim.getFloatValue()/2.0, 
                zdim.getFloatValue()/2.0));
        center.changeParentTo(World.getCS());
                
        double valZ = Math.toRadians(center.getEulerAngles().getHeading());
        double valY = Math.toRadians(center.getEulerAngles().getBank());
        double valX = Math.toRadians(center.getEulerAngles().getPitch());
        if (valZ != 0) {
            matrix.rotZ(1.0*valZ);            
        }
        if (valX != 0) {
            matrix.rotX(1.0*valX);            
        }
        if (valY != 0) {
            matrix.rotY(1.0*valY);            
        }
        matrix.setTranslation(new Vector3d(
                center.getOriginDisplacement().getX()/scale, 
                center.getOriginDisplacement().getY()/scale, 
                center.getOriginDisplacement().getZ()/scale));
        
        Transform3D transform3D = new Transform3D(matrix);
        TransformGroup transformGroup = new TransformGroup();
        transformGroup.setTransform(transform3D);
        transformGroup.addChild(getJava3DBox(red, blue, green, scale));
        List<Node> nodes = new ArrayList<Node>();
        nodes.add(transformGroup);
        return nodes;
    }

    /* (non-Javadoc)
     * @see geometry.Geometry#getScale(geometry.Box)
     */
    public Vector getScale(Geometry geometry) {
        if (geometry instanceof Box) {
            Box box = (Box) geometry;
            return Vector.create(box.xdim.getDoubleValue()/this.xdim.getDoubleValue(), 
                    box.ydim.getDoubleValue()/this.ydim.getDoubleValue(), 
                    box.zdim.getDoubleValue()/this.zdim.getDoubleValue());
        }
        System.out.println("TRYING TO GENERATE SCALE BETWEEN TWO OBJECTS THAT ARE NOT THE SAME TYPE.");
        return null;
    }
    
    public Geometry transform(CoordSys parent, Transform transformation) {
        Box box = this.copy(parent);
        Vector o = box.cs.getOriginDisplacement();
        Vector t = transformation.transformation.displacement();
        Vector sum = o.add(t);
//        Vector v = transformation.transformation.apply(o);
        box.cs.setOriginDisplacement(sum);
        EulerAngles angles = box.cs.getEulerAngles();
        EulerAngles transformedAngles = transformation.transformation.apply(angles);
        box.cs.setOrientation(transformedAngles);
        box.xdim.setValue((box.xdim.getDoubleValue() * transformation.sx));
        box.ydim.setValue((box.ydim.getDoubleValue() * transformation.sy));
        box.zdim.setValue((box.zdim.getDoubleValue() * transformation.sz));
        return box;
    }

    public boolean areEqual(Geometry geometry) {
        if (geometry instanceof Box == false) {
            return false;
        }
        Box box = (Box) geometry;
        Tolerance tolerance = Tolerance.DEFAULT;
        if(!tolerance.x.epsilonEquals(this.xdim.getDoubleValue(),box.xdim.getDoubleValue()) 
                || !tolerance.y.epsilonEquals(this.ydim.getDoubleValue(),box.ydim.getDoubleValue())
                || !tolerance.z.epsilonEquals(this.zdim.getDoubleValue(),box.zdim.getDoubleValue())) {
            return false;
        }
        return this.cs.equal(box.cs);
    }
    
    public void scale(double sx, double sy, double sz) {
        xdim.setValue((int) (xdim.getDoubleValue() * sx));
        ydim.setValue((int) (ydim.getDoubleValue() * sy));
        zdim.setValue((int) (zdim.getDoubleValue() * sz));
    }

    public void rotate(double x, double y, double z) {
        this.cs.rotate(Axis.X, x);
        this.cs.rotate(Axis.Y, y);
        this.cs.rotate(Axis.Z, z);
    }

    public Box copy(CoordSys parent) {
        Box box = new Box(xdim.getDoubleValue(), ydim.getDoubleValue(), zdim.getDoubleValue(), super.getLabels(), parent);
        box.name = new String(this.name);
        box.cs.setOriginDisplacement(this.cs.getOriginDisplacement());
        box.cs.setOrientation(this.cs.getAxialFrame());
        box.addLabels(this.getLabels());     
        return box;
    }

    public boolean intersects(Geometry geometry) {
        //TODO this only works for intersections in the x-y plane for boxes that are rotated 0, 90, 180, or 270 degrees
        if (geometry instanceof Box) {
            Box box = (Box) geometry;
            Transformation t1 = this.cs.getTransformationTo(World.getCS());
            Transformation t2 = box.cs.getTransformationTo(World.getCS()); 
            
            Point b1 = Point.create(0,0);
            Point b2 = Point.create(box.xdim.getDoubleValue(),0);
            Point b3 = Point.create(0,box.ydim.getDoubleValue());
            Point b4 = Point.create(box.xdim.getDoubleValue(), box.ydim.getDoubleValue());
            b1 = t2.apply(b1);
            b2 = t2.apply(b2);
            b3 = t2.apply(b3);
            b4 = t2.apply(b4);
            
            Point p1 = Point.create(0,0);
            Point p2 = Point.create(xdim.getDoubleValue(),0);
            Point p3 = Point.create(0,ydim.getDoubleValue());
            Point p4 = Point.create(xdim.getDoubleValue(), ydim.getDoubleValue());
            p1 = t1.apply(p1);
            p2 = t1.apply(p2);
            p3 = t1.apply(p3);
            p4 = t1.apply(p4);

            double maxXB = Math.max(b1.getX(), Math.max(b2.getX(), Math.max(b3.getX(), b4.getX())));
            double maxYB = Math.max(b1.getY(), Math.max(b2.getY(), Math.max(b3.getY(), b4.getY())));
            double minXB = Math.min(b1.getX(), Math.min(b2.getX(), Math.min(b3.getX(), b4.getX())));
            double minYB = Math.min(b1.getY(), Math.min(b2.getY(), Math.min(b3.getY(), b4.getY())));
            
            double maxXP = Math.max(p1.getX(), Math.max(p2.getX(), Math.max(p3.getX(), p4.getX())));
            double maxYP = Math.max(p1.getY(), Math.max(p2.getY(), Math.max(p3.getY(), p4.getY())));
            double minXP = Math.min(p1.getX(), Math.min(p2.getX(), Math.min(p3.getX(), p4.getX())));
            double minYP = Math.min(p1.getY(), Math.min(p2.getY(), Math.min(p3.getY(), p4.getY())));
            
            if (minXB > maxXP || minXP > maxXB) { //they are not overlapping in x --> no intersection
                return false;
            }
            if (minYB > maxYP || minYP > maxYB) { //they are not overlapping in x --> no intersection
                return false;
            }
            
            return true;
        } else {
            System.out.println("CAN'T DO INTERSECTION CALS FOR THIS TYPE");
        }
        return false;
    }

    public CoordSys getCoordSys() {
        return this.cs;
    }

    public Geometry transformInSiblingCS(Transform transform, Geometry lhsShape) {
        Box box = this.copy(this.cs.getParent());
        Geometry lhsBox = lhsShape.copy(lhsShape.getCoordSys());
        box.cs.changeParentTo(lhsBox.getCoordSys());
        lhsBox.getCoordSys().translate(transform.transformation.displacement());
        lhsBox.getCoordSys().rotate(Axis.Z, transform.transformation.orientation().getHeading());
        box.cs.changeParentTo(World.getCS());
        box.xdim.setValue((int) (box.xdim.getIntValue() * transform.sx));
        box.ydim.setValue((int) (box.ydim.getIntValue() * transform.sy));
        box.zdim.setValue((int) (box.zdim.getIntValue() * transform.sz));
        return box;
    }

    public Variable getVariable(VariableType type) {
        if (type == VariableType.X_POS || type == VariableType.Y_POS || type == VariableType.Z_POS) {
            return cs.getVariable(type);
        } else if (type == VariableType.BOX_X_DIM) {
            return xdim;
        } else if (type == VariableType.BOX_Y_DIM) {
            return ydim;
        } else if (type == VariableType.BOX_Z_DIM) {
            return zdim;
        }
        return null;
    }

    public Variable[] getVariables() {
        Variable[] pos = cs.getVariables(); 
        return new Variable[]{xdim, ydim, zdim, pos[0], pos[1], pos[2]};
    }

    public void changeParent(CoordSys cs) {
        this.cs.changeParentTo(cs);
    }

    public GNode copy() {
        return (GNode)this.copy(this.cs.getParent());
    }

    public GNode transformInSiblingCS(Transform transformation, Geometry lhsGeom, Geometry designGeom) {
        Box box = this.copy(designGeom.getCoordSys());
        box.cs.changeParentTo(World.getCS());
        return box;
    }
}
